package com.splunk.modularinput;

import com.splunk.SDKTestCase;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.w3c.dom.Document;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.TransformerException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.Date;

/**
 * Test the behavior of subclasses of the Script class. This is to check whether modular inputs really
 * do what they're supposed to.
 */
public class ScriptTest extends ModularInputTestCase {
    EventWriter eventWriter;
    StringWriter out, err;
    InputStream in;

    @Before
    public void setUp() throws XMLStreamException {
        out = new StringWriter();
        err = new StringWriter();

        eventWriter = new EventWriter(out, err);
    }

    /*
     * A script that returns a null scheme should generate no output on stdout and an error on stderr
     * saying that it the scheme was null.
     */
    @Test
    public void testErrorOnScriptWithNullScheme() throws XMLStreamException {
        Script script = new Script() {
            @Override
            public Scheme getScheme() {
                return null;
            }

            @Override
            public void streamEvents(InputDefinition inputs, EventWriter ew) throws
                    MalformedDataException, XMLStreamException {
                // Not used.
            }
        };


        String[] args = new String[] {"--scheme"};
        int returnValue = script.run(args, eventWriter, in);

        Assert.assertEquals("", out.toString());
        Assert.assertEquals("FATAL Modular input script returned a null scheme.\n", err.toString());
        Assert.assertNotEquals(0, returnValue);
    }

    /*
     * Check that a scheme generated by a script is what we expect.
     */
    @Test
    public void testSchemeProperlyGeneratedByScript() throws TransformerException, ParserConfigurationException {
        Script script = new Script() {
            @Override
            public Scheme getScheme() {
                Scheme scheme = new Scheme("abcd");
                scheme.setDescription("\uC3BC and \uC3B6 and <&> f\u00FCr");
                scheme.setStreamingMode(Scheme.StreamingMode.SIMPLE);
                scheme.setUseExternalValidation(false);
                scheme.setUseSingleInstance(true);

                Argument arg1 = new Argument("arg1");
                scheme.addArgument(arg1);

                Argument arg2 = new Argument("arg2");
                arg2.setDescription("\uC3BC and \uC3B6 and <&> f\u00FCr");
                arg2.setDataType(Argument.DataType.NUMBER);
                arg2.setRequiredOnCreate(true);
                arg2.setRequiredOnEdit(true);
                arg2.setValidation("is_pos_int('some_name')");
                scheme.addArgument(arg2);

                return scheme;
            }

            @Override
            public void streamEvents(InputDefinition inputs, EventWriter ew) throws MalformedDataException, XMLStreamException {
                // Not used.
            }
        };

        String[] args = new String[] {"--scheme"};
        int returnValue = script.run(args, eventWriter, in);

        Assert.assertEquals("", err.toString());
        Assert.assertEquals(0, returnValue);

        Document found = stringToXmlDocument(out.toString());
        Document expected = resourceToXmlDocument("modularinput/data/scheme_without_defaults.xml");

        assertXmlEqual(expected, found);
    }

    /*
     * Check that successful validation yield no text and a 0 exit value.
     */
    @Test
    public void testSuccessfulValidation() throws UnsupportedEncodingException {
        Script script = new Script() {
            @Override
            public Scheme getScheme() {
                return null;  // Unused
            }

            @Override
            public void validateInput(ValidationDefinition definition) throws Exception {
                // Always succeed
            }

            @Override
            public void streamEvents(InputDefinition inputs, EventWriter ew) throws MalformedDataException, XMLStreamException {
                // Unused
            }
        };

        String[] args = new String[] {"--validate-arguments"};
        int returnValue = script.run(
                args,
                eventWriter,
                SDKTestCase.openResource("modularinput/data/validation.xml")
        );

        Assert.assertEquals("", err.toString());
        Assert.assertEquals("", out.toString());
        Assert.assertEquals(0, returnValue);
    }

    /*
     * Check that failed validation writes sensible XML to stdout.
     */
    @Test
    public void testFailedValidation() throws TransformerException, ParserConfigurationException {
        Script script = new Script() {
            @Override
            public Scheme getScheme() {
                return null;  // Unused
            }

            @Override
            public void validateInput(ValidationDefinition definition) throws Exception {
                throw new MalformedDataException("Oh, the vogonity!");
            }

            @Override
            public void streamEvents(InputDefinition inputs, EventWriter ew) throws MalformedDataException, XMLStreamException {
                // Unused
            }
        };

        String[] args = new String[] {"--validate-arguments"};
        int returnValue = script.run(
                args,
                eventWriter,
                SDKTestCase.openResource("modularinput/data/validation.xml")
        );

        Document expectedXml = resourceToXmlDocument("modularinput/data/validation_error.xml");
        Document foundXml = stringToXmlDocument(out.toString());

        Assert.assertEquals("", err.toString());
        assertXmlEqual(expectedXml, foundXml);
        Assert.assertNotEquals(0, returnValue);
    }

    /*
     * Check that passing an input definition and writing a couple events goes smoothly.
     */
    @Test
    public void testWriteEvents() throws TransformerException, ParserConfigurationException {
        Script script = new Script() {

            @Override
            public Scheme getScheme() {
                return null;  // Unused
            }

            @Override
            public void streamEvents(InputDefinition inputs, EventWriter ew) throws MalformedDataException,
                    XMLStreamException {
                Event event = new Event();
                event.setTime(new Date(1372275124466L));
                event.setStanza("fubar");
                event.setData("This is a test of the emergency broadcast system.");
                event.setHost("localhost");
                event.setIndex("main");
                event.setSource("hilda");
                event.setSourceType("misc");
                event.setDone(true);
                event.setUnbroken(true);

                ew.writeEvent(event);
                ew.writeEvent(event);
            }
        };

        String[] args = new String[] {};
        InputStream input = SDKTestCase.openResource("modularinput/data/conf_with_2_inputs.xml");

        int returnValue = script.run(args, eventWriter, input);

        Assert.assertEquals(0, returnValue);
        Assert.assertEquals("", err.toString());

        Document expectedXml = resourceToXmlDocument("modularinput/data/stream_with_two_events.xml");
        Document foundXml = stringToXmlDocument(out.toString());
        assertXmlEqual(expectedXml, foundXml);

    }

}
